package com.xprophet.crs.jpeg.codec.impl;

import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;

import com.xprophet.crs.jpeg.codec.AbstractJPEGDecoder;
import com.xprophet.crs.jpeg.codec.FingerprintType;
import com.xprophet.crs.jpeg.codec.IJPEGDecoder;
import com.xprophet.crs.jpeg.codec.model.Fingerprint;

public class SnoopDecoderImpl extends AbstractJPEGDecoder implements IJPEGDecoder {
	
	static Logger log = Logger.getLogger(SnoopDecoderImpl.class);
	
	public static final String ID = "snoop";
	
	public static final String RESOURCE_SNOOP_SIGNS = "signs.txt";
	
	static NumberFormat format = NumberFormat.getInstance();
	
	static {
		format.setMinimumIntegerDigits(3);
	}
	
	private static final Map<String, List<Fingerprint>> matchMap = new HashMap<String, List<Fingerprint>>();//Collections.unmodifiableMap(new HashMap<String, List<Fingerprint>>());
	
	public SnoopDecoderImpl() {
		
	}
	
	public SnoopDecoderImpl(List<Fingerprint> list) {
		updateMatchMap(list);
	}
	
	private void updateMatchMap(List<Fingerprint> list) {
		if (list != null) {
			for (Fingerprint fp : list) {
				updateMatchMap(fp);
			}
		}
	}
	
	public String getData() {
		if (info == null) {
			return null;
		}
		
		int[] cmatrix = info.getDQT0();
		int[] imatrix = info.getDQT1();
		if (cmatrix == null || imatrix == null) {
			return null;
		}
		
		StringBuilder buf = new StringBuilder("JPEGsnoop*DQT0,");
		for (int i = 0; i <= QTABLE_MAX_IDX; i++) {
			buf.append(format.format(cmatrix[i]));
			if (i < QTABLE_MAX_IDX)
				buf.append(",");
		}
		buf.append(",*DQT1,");
		for (int i = 0; i <= QTABLE_MAX_IDX; i++) {
			buf.append(format.format(imatrix[i]));
			if (i < QTABLE_MAX_IDX)
				buf.append(",");
		}
		buf.append(",*END");

		info.setDQTString(buf.toString());
		return info.getDQTString();
	}

	public String getEncryptedData() {
		this.getData();
		if (info == null || info.getDQTString() == null) {
			return "";
		}
		
		MessageDigest messageDigest = null;
		try {
			messageDigest = MessageDigest.getInstance("MD5");
			messageDigest.reset();
			messageDigest.update(info.getDQTString().getBytes("UTF-8"));

		} catch (NoSuchAlgorithmException e) {
			log.error("", e);
			return "";
		} catch (UnsupportedEncodingException e) {
			log.error("", e);
			return "";
		}

		byte[] byteArray = messageDigest.digest();
		StringBuilder md5StrBuff = new StringBuilder("01");
		for (int i = 1 /* omit index 0 */; i < byteArray.length; i++) {
			String tmpStr = Integer.toHexString(0xFF & byteArray[i]);
			if (tmpStr.length() == 1) {
				md5StrBuff.append("0");
			}
			md5StrBuff.append(tmpStr);
		}
		
		Fingerprint fp = info.getFingerprint() == null ? new Fingerprint() : info.getFingerprint();
		fp.setSnoop_sig(md5StrBuff.toString().toUpperCase());
		info.setFingerprint(fp);
		return fp.getSnoop_sig();
	}
	
	public void readSnoopMapFromFile() {
		List<String> lines = new ArrayList<String>();
		InputStream input = IJPEGDecoder.class.getResourceAsStream(RESOURCE_SNOOP_SIGNS);
		try {
			lines = IOUtils.readLines(input, "UTF-8");
		} catch (IOException e1) {
			e1.printStackTrace();
			return;
		}
		
		for (String line : lines) {
			if (StringUtils.isBlank(line)) {
				continue;
			}
			String[] tmpArr = StringUtils.splitByWholeSeparatorPreserveAllTokens(line.replace(",\"", "]").replace("\"", ""), "]");
			Fingerprint fp = new Fingerprint();
			fp.setType(Integer.parseInt(tmpArr[0].trim()));
			fp.setMake(tmpArr[1]);
			fp.setModel(tmpArr[2]);
			fp.setUm_quality(tmpArr[3]);
			fp.setSnoop_sig(tmpArr[4]);
			fp.setSnoop_sig_rot(tmpArr[5]);
			fp.setSubsamp(tmpArr[6]);
			fp.setSwtrim(tmpArr[7]);
			fp.setSwdisp(tmpArr[8]);
			
//			fingerprintList.add(fp);
			// one key might map to multi values
			String key = fp.getSnoop_sig();
			if (matchMap.containsKey(key)) {
				matchMap.get(key).add(fp);
			} else {
				List<Fingerprint> tmpList = new ArrayList<Fingerprint>();
				tmpList.add(fp);
				matchMap.put(key, tmpList);
			}
		}
//		log.debug("Total records: " + fingerprintList.size());
		log.debug("Map keys: " + matchMap.keySet().size());
	}
	
	public Fingerprint getMatchResult(String key, Fingerprint lastResult) {
		Fingerprint retval = lastResult;
		if (retval == null) {
			retval = new Fingerprint();
		}
		retval.setSnoop_sig(key);
		
		String make = exif.getMake();
		String model = exif.getModel();
		String editor = exif.getEditor();
		retval.setMake(make);
		retval.setModel(model);
		retval.setEditor(editor);
		
//		if (make != null || model != null || editor != null) {
//			log.debug(make + "_" + model + "_" + editor);
//		}

		List<Fingerprint> fps = matchMap.get(key);
		if (fps == null) {
			return retval;
		} else {
			for (Fingerprint fp : fps) {
				//TODO: need optimize: to see possible matches
				if (fp.getType() == FingerprintType.HARDWARE) {
					if (make != null && make.indexOf(fp.getMake()) != -1) {
//						retval.setMake(fp.getMake());
//						int diffCount = StringUtils.indexOfDifference(fp.getModel(), model);
//						if (diffCount == -1) { // exactly match
						if (fp.getModel().equalsIgnoreCase(model)) {
							fp.cloneHardware(retval);
							return retval;
						} 
//						else if (diffCount >= 2) {
//							//TODO: add new model
//							log.debug("NEW MODEL: " + this.getID() + ": " + fp);
//							retval.setType(FingerprintType.HARDWARE);
//							return retval;
//						} 
					}
				} else if (fp.getType() == FingerprintType.SOFTWARE) {
					if (editor != null && editor.indexOf(fp.getMake()) != -1) {
						fp.cloneSoftware(retval);
						return retval;
					}
				}
			}
		}
//		if (fps.size() > 0) {
//			Fingerprint randomResult = fps.get(0);
//			if (FingerprintType.SOFTWARE == randomResult.getType()) {
//				retval.setEditor(randomResult.getMake());
//			} else if (FingerprintType.HARDWARE == randomResult.getType()) {
//				retval.setMake(randomResult.getMake());
//				retval.setModel(randomResult.getModel());
//			}
//			log.debug("RANDOM MODEL: " + this.getID() + ": " + retval);
//		}
		return retval;
	}

	public String getID() {
		return SnoopDecoderImpl.ID;
	}

	public Fingerprint getExif() {
		return exif;
	}

	public void setExif(Fingerprint exif) {
		this.exif = exif;
	}

	public Map<String, List<Fingerprint>> getMatchMap() {
		return matchMap;
	}

	public void updateMatchMap(Fingerprint newValue) {
		String sig = newValue.getSnoop_sig();
		List<Fingerprint> value = matchMap.get(sig);
		if (value == null) {
			value = new ArrayList<Fingerprint>();
			value.add(newValue);
			matchMap.put(sig, value);
		} else {
			value.add(newValue);
		}
	}

}
